Micron Document
🎖️GitЯра🎖️

Node / meshtastic / Meshtastic-Android / files / core / takserver / src / commonTest / kotlin / org / meshtastic / core / takserver / TAKPacketV2RawDetailTest.kt

Displaying Raw • Download

core/takserver/src/commonTest/kotlin/org/meshtastic/core/takserver/TAKPacketV2RawDetailTest.kt 65a1f5ce4d0e4b8eafcd0bbfccafd41dc6ee888a (65a1f5ce) Text, 6.92 KB

T8b949e/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Tff7b72package T7ee787org.meshtastic.core.takserver

Tff7b72import T7ee787org.meshtastic.core.takserver.TAKPacketV2Conversion.toCoTMessage
Tff7b72import T7ee787org.meshtastic.core.takserver.TAKPacketV2Conversion.toTAKPacketV2
Tff7b72import T7ee787kotlin.test.Test
Tff7b72import T7ee787kotlin.test.assertEquals
Tff7b72import T7ee787kotlin.test.assertFalse
Tff7b72import T7ee787kotlin.test.assertNotNull
Tff7b72import T7ee787kotlin.test.assertNull
Tff7b72import T7ee787kotlin.test.assertTrue

T8b949e/**
* Verifies the `raw_detail` fallback round-trip for CoT types that don't fit any structured
* [org.meshtastic.proto.TAKPacketV2] payload (PLI, GeoChat, Aircraft).
*
* Prior to this, ATAK user-drawn elements like `u-d-c-c` would be silently dropped by
* [TAKPacketV2Conversion.toTAKPacketV2] with `"Cannot convert CoT to TAKPacketV2 for type ..."`.
*/
Tff7b72class T56d364TAKPacketV2RawDetailTest Tb4b4b4{

Tf0883e@Test
Tff7b72fun Td2a8ffudcc_round_trips_via_raw_detailTb4b4b4(Tb4b4b4) Tb4b4b4{
T8b949e// Note: `<shape>` / `<labels_on>` / `<color>` in the input are deliberately
T8b949e// stripped by [CoTDetailStripper] before being placed in raw_detail, because
T8b949e// they blow up the wire size beyond the LoRa MTU. We keep `<contact>` here so
T8b949e// we have something non-trivial to verify round-tripped.
Tff7b72val Te6edf3shapeXml Tff7b72=
Ta5d6ff"""
Ta5d6ff <event version=Ta5d6ff"Ta5d6ff2.0Ta5d6ff"Ta5d6ff uid=Ta5d6ff"Ta5d6ffcircle-abcTa5d6ff"Ta5d6ff type=Ta5d6ff"Ta5d6ffu-d-c-cTa5d6ff"Ta5d6ff time=Ta5d6ff"Ta5d6ff2025-01-01T12:00:00.000ZTa5d6ff"Ta5d6ff start=Ta5d6ff"Ta5d6ff2025-01-01T12:00:00.000ZTa5d6ff"Ta5d6ff stale=Ta5d6ff"Ta5d6ff2025-01-01T13:00:00.000ZTa5d6ff"Ta5d6ff how=Ta5d6ff"Ta5d6ffh-eTa5d6ff"Ta5d6ff>
<point lat=Ta5d6ff"Ta5d6ff45.5Ta5d6ff"Ta5d6ff lon=Ta5d6ff"Ta5d6ff-90.25Ta5d6ff"Ta5d6ff hae=Ta5d6ff"Ta5d6ff0Ta5d6ff"Ta5d6ff ce=Ta5d6ff"Ta5d6ff10.0Ta5d6ff"Ta5d6ff le=Ta5d6ff"Ta5d6ff10.0Ta5d6ff"Ta5d6ff/>
<detail>
<contact callsign=Ta5d6ff"Ta5d6ffALPHA01Ta5d6ff"Ta5d6ff/>
<shape>
<ellipse major=Ta5d6ff"Ta5d6ff500Ta5d6ff"Ta5d6ff minor=Ta5d6ff"Ta5d6ff500Ta5d6ff"Ta5d6ff angle=Ta5d6ff"Ta5d6ff0Ta5d6ff"Ta5d6ff/>
<link line=Ta5d6ff"Ta5d6ff#ff0000Ta5d6ff"Ta5d6ff width=Ta5d6ff"Ta5d6ff3Ta5d6ff"Ta5d6ff/>
</shape>
<labels_on value=Ta5d6ff"Ta5d6fffalseTa5d6ff"Ta5d6ff/>
</detail>
</event>
Ta5d6ff"""
Tb4b4b4.Te6edf3trimIndentTb4b4b4(Tb4b4b4)

T8b949e// Parse → convert to TAKPacketV2
Tff7b72val Te6edf3cotMessage Tff7b72= Te6edf3CoTXmlParserTb4b4b4(Te6edf3shapeXmlTb4b4b4)Tb4b4b4.Te6edf3parseTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3getOrNullTb4b4b4(Tb4b4b4)
Te6edf3assertNotNullTb4b4b4(Te6edf3cotMessageTb4b4b4, Ta5d6ff"Ta5d6ffCoT XML must parse successfullyTa5d6ff"Tb4b4b4)
Tff7b72val Te6edf3takPacketV2 Tff7b72= Te6edf3cotMessageTb4b4b4.Te6edf3toTAKPacketV2Tb4b4b4(Tb4b4b4)
Te6edf3assertNotNullTb4b4b4(Te6edf3takPacketV2Tb4b4b4, Ta5d6ff"Ta5d6ffu-d-c-c must convert to TAKPacketV2 (not drop)Ta5d6ff"Tb4b4b4)

T8b949e// raw_detail must be populated; structured payloads must be null.
Te6edf3assertNotNullTb4b4b4(Te6edf3takPacketV2Tb4b4b4.Te6edf3raw_detailTb4b4b4, Ta5d6ff"Ta5d6ffraw_detail must hold the detail bytesTa5d6ff"Tb4b4b4)
T8b949e// v0.4.0: the `bool pli` oneof arm was removed (PLI is implicit). A populated
T8b949e// raw_detail already proves this is NOT an implicit PLI.
Te6edf3assertNullTb4b4b4(Te6edf3takPacketV2Tb4b4b4.Te6edf3chatTb4b4b4, Ta5d6ff"Ta5d6ffchat payload must not be set for u-d-c-cTa5d6ff"Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ffu-d-c-cTa5d6ff"Tb4b4b4, Te6edf3takPacketV2Tb4b4b4.Te6edf3cot_type_strTb4b4b4.Te6edf3ifEmpty Tb4b4b4{ Ta5d6ff"Ta5d6ffu-d-c-cTa5d6ff" Tb4b4b4}Tb4b4b4)
T8b949e// Stripping must have fired: the raw_detail bytes must NOT contain the
T8b949e// shape/labels_on fragments we put in the input.
Tff7b72val Te6edf3rawDetailBytes Tff7b72= Te6edf3takPacketV2Tb4b4b4.Te6edf3raw_detailTff7b72!!Tb4b4b4.Te6edf3utf8Tb4b4b4(Tb4b4b4)
Te6edf3assertFalseTb4b4b4(Te6edf3rawDetailBytesTb4b4b4.Te6edf3containsTb4b4b4(Ta5d6ff"Ta5d6ffshapeTa5d6ff"Tb4b4b4)Tb4b4b4, Ta5d6ff"Ta5d6ffshape must be stripped from raw_detail: Tffd700$Te6edf3rawDetailBytesTa5d6ff"Tb4b4b4)
Te6edf3assertFalseTb4b4b4(Te6edf3rawDetailBytesTb4b4b4.Te6edf3containsTb4b4b4(Ta5d6ff"Ta5d6fflabels_onTa5d6ff"Tb4b4b4)Tb4b4b4, Ta5d6ff"Ta5d6fflabels_on must be stripped: Tffd700$Te6edf3rawDetailBytesTa5d6ff"Tb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3rawDetailBytesTb4b4b4.Te6edf3containsTb4b4b4(Ta5d6ff"Ta5d6ffcontactTa5d6ff"Tb4b4b4)Tb4b4b4, Ta5d6ff"Ta5d6ffcontact must survive: Tffd700$Te6edf3rawDetailBytesTa5d6ff"Tb4b4b4)

T8b949e// Convert back to CoTMessage
Tff7b72val Te6edf3roundTripped Tff7b72= Te6edf3takPacketV2Tb4b4b4.Te6edf3toCoTMessageTb4b4b4(Tb4b4b4)
Te6edf3assertNotNullTb4b4b4(Te6edf3roundTrippedTb4b4b4, Ta5d6ff"Ta5d6ffTAKPacketV2 with raw_detail must convert back to CoTMessageTa5d6ff"Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ffu-d-c-cTa5d6ff"Tb4b4b4, Te6edf3roundTrippedTb4b4b4.Te6edf3typeTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff4T79c0ff5.5Tb4b4b4, Te6edf3roundTrippedTb4b4b4.Te6edf3latitudeTb4b4b4, T79c0ff0.0001Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(Tff7b72-T79c0ff9T79c0ff0.25Tb4b4b4, Te6edf3roundTrippedTb4b4b4.Te6edf3longitudeTb4b4b4, T79c0ff0.0001Tb4b4b4)

T8b949e// Serialize to XML; the surviving (stripped) content must be present.
Tff7b72val Te6edf3xmlOut Tff7b72= Te6edf3roundTrippedTb4b4b4.Te6edf3toXmlTb4b4b4(Tb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3xmlOutTb4b4b4.Te6edf3containsTb4b4b4(Ta5d6ff"Ta5d6fftype='u-d-c-c'Ta5d6ff"Tb4b4b4)Tb4b4b4, Ta5d6ff"Ta5d6fftype must survive: Tffd700$Te6edf3xmlOutTa5d6ff"Tb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3xmlOutTb4b4b4.Te6edf3containsTb4b4b4(Ta5d6ff"Ta5d6ffALPHA01Ta5d6ff"Tb4b4b4)Tb4b4b4, Ta5d6ff"Ta5d6ffcontact callsign must survive: Tffd700$Te6edf3xmlOutTa5d6ff"Tb4b4b4)
Te6edf3assertFalseTb4b4b4(Te6edf3xmlOutTb4b4b4.Te6edf3containsTb4b4b4(Ta5d6ff"Ta5d6ff<shapeTa5d6ff"Tb4b4b4)Tb4b4b4, Ta5d6ff"Ta5d6ffshape must not reappear on receive: Tffd700$Te6edf3xmlOutTa5d6ff"Tb4b4b4)
Te6edf3assertFalseTb4b4b4(Te6edf3xmlOutTb4b4b4.Te6edf3containsTb4b4b4(Ta5d6ff"Ta5d6ff<labels_onTa5d6ff"Tb4b4b4)Tb4b4b4, Ta5d6ff"Ta5d6fflabels_on must not reappear: Tffd700$Te6edf3xmlOutTa5d6ff"Tb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ffraw_detail_path_emits_only_the_raw_bytes_inside_detail_no_duplicate_structured_elementsTb4b4b4(Tb4b4b4) Tb4b4b4{
T8b949e// If toCoTMessage populated contact/group/status on the raw_detail path, toXml would
T8b949e// double-emit them alongside the rawDetailXml content. Guard against that regression.
Tff7b72val Te6edf3xml Tff7b72=
Ta5d6ff"""
Ta5d6ff <event version=Ta5d6ff"Ta5d6ff2.0Ta5d6ff"Ta5d6ff uid=Ta5d6ff"Ta5d6ffmarker-1Ta5d6ff"Ta5d6ff type=Ta5d6ff"Ta5d6ffb-m-p-s-p-iTa5d6ff"Ta5d6ff time=Ta5d6ff"Ta5d6ff2025-01-01T12:00:00.000ZTa5d6ff"Ta5d6ff start=Ta5d6ff"Ta5d6ff2025-01-01T12:00:00.000ZTa5d6ff"Ta5d6ff stale=Ta5d6ff"Ta5d6ff2025-01-01T13:00:00.000ZTa5d6ff"Ta5d6ff how=Ta5d6ff"Ta5d6ffh-eTa5d6ff"Ta5d6ff>
<point lat=Ta5d6ff"Ta5d6ff10.0Ta5d6ff"Ta5d6ff lon=Ta5d6ff"Ta5d6ff20.0Ta5d6ff"Ta5d6ff hae=Ta5d6ff"Ta5d6ff0Ta5d6ff"Ta5d6ff ce=Ta5d6ff"Ta5d6ff0Ta5d6ff"Ta5d6ff le=Ta5d6ff"Ta5d6ff0Ta5d6ff"Ta5d6ff/>
<detail>
<contact callsign=Ta5d6ff"Ta5d6ffDROP-1Ta5d6ff"Ta5d6ff/>
<__group name=Ta5d6ff"Ta5d6ffRedTa5d6ff"Ta5d6ff role=Ta5d6ff"Ta5d6ffTeam MemberTa5d6ff"Ta5d6ff/>
<color argb=Ta5d6ff"Ta5d6ff-65536Ta5d6ff"Ta5d6ff/>
</detail>
</event>
Ta5d6ff"""
Tb4b4b4.Te6edf3trimIndentTb4b4b4(Tb4b4b4)

Tff7b72val Te6edf3cotMessage Tff7b72= Te6edf3CoTXmlParserTb4b4b4(Te6edf3xmlTb4b4b4)Tb4b4b4.Te6edf3parseTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3getOrNullTb4b4b4(Tb4b4b4)Tff7b72!!
Tff7b72val Te6edf3takPacketV2 Tff7b72= Te6edf3cotMessageTb4b4b4.Te6edf3toTAKPacketV2Tb4b4b4(Tb4b4b4)Tff7b72!!
Tff7b72val Te6edf3roundTripped Tff7b72= Te6edf3takPacketV2Tb4b4b4.Te6edf3toCoTMessageTb4b4b4(Tb4b4b4)Tff7b72!!

Te6edf3assertNullTb4b4b4(Te6edf3roundTrippedTb4b4b4.Te6edf3contactTb4b4b4, Ta5d6ff"Ta5d6ffcontact must be null on raw_detail path (lives inside rawDetailXml)Ta5d6ff"Tb4b4b4)
Te6edf3assertNullTb4b4b4(Te6edf3roundTrippedTb4b4b4.Te6edf3groupTb4b4b4, Ta5d6ff"Ta5d6ffgroup must be null on raw_detail pathTa5d6ff"Tb4b4b4)
Te6edf3assertNullTb4b4b4(Te6edf3roundTrippedTb4b4b4.Te6edf3statusTb4b4b4, Ta5d6ff"Ta5d6ffstatus must be null on raw_detail pathTa5d6ff"Tb4b4b4)

Tff7b72val Te6edf3xmlOut Tff7b72= Te6edf3roundTrippedTb4b4b4.Te6edf3toXmlTb4b4b4(Tb4b4b4)
T8b949e// Exactly one <contact> (from the round-tripped raw detail), not two.
Te6edf3assertEqualsTb4b4b4(T79c0ff1Tb4b4b4, Te6edf3xmlOutTb4b4b4.Te6edf3splitTb4b4b4(Ta5d6ff"Ta5d6ff<contactTa5d6ff"Tb4b4b4)Tb4b4b4.Te6edf3size Tff7b72- T79c0ff1Tb4b4b4, Ta5d6ff"Ta5d6ffonly one contact element allowed: Tffd700$Te6edf3xmlOutTa5d6ff"Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff1Tb4b4b4, Te6edf3xmlOutTb4b4b4.Te6edf3splitTb4b4b4(Ta5d6ff"Ta5d6ff<__groupTa5d6ff"Tb4b4b4)Tb4b4b4.Te6edf3size Tff7b72- T79c0ff1Tb4b4b4, Ta5d6ff"Ta5d6ffonly one group element allowed: Tffd700$Te6edf3xmlOutTa5d6ff"Tb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ffcotMessageWithoutParsedDetailReturnsNullTb4b4b4(Tb4b4b4) Tb4b4b4{
T8b949e// CoTMessage created in-app (no XML round trip) for an unmapped type has no parsed
T8b949e// detail to fall back on — conversion should return null.
Tff7b72val Te6edf3cot Tff7b72=
Te6edf3CoTMessageTb4b4b4(
Te6edf3uid Tff7b72= Ta5d6ff"Ta5d6ffmanual-1Ta5d6ff"Tb4b4b4,
Te6edf3type Tff7b72= Ta5d6ff"Ta5d6ffu-d-c-cTa5d6ff"Tb4b4b4,
Te6edf3stale Tff7b72= Te6edf3kotlinTb4b4b4.Te6edf3timeTb4b4b4.Te6edf3ClockTb4b4b4.Te6edf3SystemTb4b4b4.Te6edf3nowTb4b4b4(Tb4b4b4) Tff7b72+ Te6edf3kotlinTb4b4b4.Te6edf3timeTb4b4b4.Te6edf3DurationTb4b4b4.Te6edf3parseTb4b4b4(Ta5d6ff"Ta5d6ff1hTa5d6ff"Tb4b4b4)Tb4b4b4,
Te6edf3latitude Tff7b72= T79c0ff0.0Tb4b4b4,
Te6edf3longitude Tff7b72= T79c0ff0.0Tb4b4b4,
Tb4b4b4)
Te6edf3assertNullTb4b4b4(Te6edf3cotTb4b4b4.Te6edf3toTAKPacketV2Tb4b4b4(Tb4b4b4)Tb4b4b4, Ta5d6ff"Ta5d6ffno parsed detail → no raw_detail fallback possibleTa5d6ff"Tb4b4b4)
Tb4b4b4}
Tb4b4b4}

Served by rngit 1.5.0 - Generated in 0.05s